import './Gobang.css';
import { Component } from "react";

const DEFAULT_CODE = -1;
const BLACK_CODE = 0;
const WHILE_CODE = 1;
const FIR_CODE = 6;

const PLAY_MODE = {
  SINGLE:0,
  PVP:1,
  PVAI:2
}

export default
class Gobang extends Component{

  constructor(props){
    super(props);

    const size = props.Size || 32;
    const rows = props.Rows || 15;
    const cols = props.Cols || 15;

    this.init( rows, cols );

    this.playerA = { name:"PlayerA", key:"A", isBlack:true };
    this.playerB = { name:"PlayerB", key:"B", isBlack:false };

    this.state = {
      // board
      size: size,
      rows: rows,
      cols: cols,

      showSteps:false,

      gameClock: 0,

      // mode
      mode: PLAY_MODE.SINGLE,
      //server
      server:"192.168.31.83:3001",
      myName:"player",
      room: 6,
      role: -1,
      players:[]
    }

    this.handlePieceClicked = this.handlePieceClicked.bind(this);
    this.handleModeChanged = this.handleModeChanged.bind(this);
    this.handlePlayerChanged = this.handlePlayerChanged.bind(this);
    this.handleValueChanged = this.handleValueChanged.bind(this);
    this.timer = this.timer.bind(this);

    this.connect = this.connect.bind(this);
    this.disconnect = this.disconnect.bind(this);
    this.setRole = this.setRole.bind(this);
    this.restart = this.restart.bind(this);
  }

  // 初始化游戏
  init( rows, cols ) {

    const board = [];
    const steps = [];
    const movements = [];

    for( var i = 0; i <= rows; i++ ) {
      board[i] = [];
      steps[i] = [];
      for( var j = 0; j <= cols; j++ ) {
        board[i][j] = DEFAULT_CODE;
        steps[i][j] = 0;
      }
    }

    this.board = board;         // 棋盘
    this.steps = steps;         // 步数
    this.movements = movements; // 移动队列

    this.isStart = false;
    this.isOver = false;
    this.winner = -1;

  }

  componentDidMount(){
    // websocket
    this.socket = null;
    this.myKey = "";
  }

  componentWillUnmount(){
    this.disconnect();
  }
  // 获取可移动队列
  getMovealbePositions(){
    const board = this.board;
    const positions = [];
    for( var i = 0, il = board.length; i < il; i++ ) {
      for( var j = 0, jl = board[i].length; j < jl; j++ ) {
        if( board[i][j] === DEFAULT_CODE ) {
          positions.push([i, j]);
        }
      }
    }
    return positions;
  }
  // AI
  createRandomPosition(){
    const positions = this.getMovealbePositions();
    if( positions.length <= 0 ) return [-1, -1];

    return positions[Math.floor(Math.random() * positions.length)];
  }

  aiMove(){
    // 随机
    const pos = this.createRandomPosition();
    if( pos[0] < 0 || pos[1] < 0 ){
      console.error('AI 傻掉了...');
      this.gameover( this.playerA.isBlack ? 0 : 1 );
    }
    else {
      this.move( pos[0], pos[1], this.movements.length % 2 );
    }
    
  }
  // ms => h:m:s
  toTimeString( d ) {
    d = Math.round( parseFloat (d) / 1000 ); // ms => s
    const h = Math.floor( d / 3600);         // hour
    const m = Math.floor( (d % 3600) / 60 ); // minute
    const s = Math.floor( (d % 3600) % 60 );        // second
    return `${h<10?"0":""}${h}:${m<10?"0":""}${m}:${s<10?"0":""}${s}`;
  }
  // 模式切换
  handleModeChanged(event) {
    // 断开socket连接
    this.disconnect()
    const mode = parseInt( event.target.value );
    this.reset();
    
    if( mode === PLAY_MODE.PVAI ) { // AI
      this.playerB.name = "AI";
      this.playerB.isBlack = !this.playerA.isBlack;
    }

    this.setState({ mode: mode });

  }
  // 切换黑白角色
  handlePlayerChanged( event ) {
    const mode = parseInt( this.state.mode );

    if( mode === PLAY_MODE.SINGLE || mode === PLAY_MODE.PVP ) return;
    if( this.isStart ) return;

    this.playerA.isBlack = !this.playerA.isBlack;
    this.playerB.isBlack = !this.playerA.isBlack;
    this.forceUpdate();
  }
  // 服务器参数改变
  handleValueChanged(event) {
    const id = event.target.id;
    // console.log( id, event.target.value )
    switch (id) {
      case 'server':
        this.setState({ server: event.target.value });
        break;
      case 'room':
        this.setState({ room: event.target.value });
        break;
      case 'name':
        this.setState({ myName: event.target.value });
        break;
      default:
        break;
    }
    
  }
  // 全局计时器
  timer() {
    if( this.isOver ) {
      console.log("game over");
      if( this.gameTimer ){
        window.clearInterval( this.gameTimer );
      }
      return;
    }
    const nowTime  = performance.now();
    this.setState({ gameClock: (nowTime-this.gameStart) });
  }
  // 仅设置棋盘
  set( r, c, v ) {

    r = parseInt(r);
    c = parseInt(c);
    v = parseInt(v);

    const board = this.board;

    if( r < 0 || r >= board.length ) return false;
    if( c < 0 || c >= board[r].length ) return false;

    board[r][c] = v;
    return true;

  }
  // 下棋
  move( r, c, v ) {
    
    if( this.isOver ) return false;

    r = parseInt(r);
    c = parseInt(c);
    v = parseInt(v);

    if( this.board[r][c] !== DEFAULT_CODE ) return false;

    if( !this.set( r , c, v ) ) return false;

    const movements = this.movements;
    const steps = this.steps;

    movements.push({
      row:r, col:c, value:v
    });

    steps[r][c] = movements.length;

    // 自动开始计时
    if( !this.isStart && movements.length === 1 ) {
      this.start( false );
    }

    // check
    this.check( r, c );

    return true;
  }
  // 悔棋
  unmove() {

    if( this.isOver || !this.isStart ) return false;
    const mode = parseInt( this.state.mode );

    if( mode === PLAY_MODE.PVP ) return;

    const movements = this.movements;
    const steps = this.steps;

    if( movements.length <= 0 ) return false;

    let lastMove;
    // AI对战需要撤销两次
    if( mode === PLAY_MODE.PVAI && this.isMyTurn() ){
      lastMove = movements.pop();
      steps[lastMove.row][lastMove.col] = DEFAULT_CODE;
      this.set( lastMove.row, lastMove.col, DEFAULT_CODE );

      lastMove = movements.pop();
      steps[lastMove.row][lastMove.col] = DEFAULT_CODE;
      this.set( lastMove.row, lastMove.col, DEFAULT_CODE );
    }
    else{
      lastMove = movements.pop();
      steps[lastMove.row][lastMove.col] = DEFAULT_CODE;

      this.set( lastMove.row, lastMove.col, DEFAULT_CODE );
    }

    return true;
  }
  // 开始游戏
  start( doReset = true ) {
    console.log("game start");

    if( doReset ) this.reset();

    this.isStart = true;
    this.isOver = false;

    this.gameStart = performance.now();

    const mode = parseInt( this.state.mode );

    // if( mode === PLAY_MODE.PVP && this.isConnect() && doReset){
    //   this.socket.send('RESET:');
    // }
    
    if( mode === PLAY_MODE.PVAI && this.playerB.isBlack ) {
      this.aiMove();
    }

    this.gameTimer = window.setInterval( this.timer, 100 );
    this.setState({
      gameClock:0
    })
    
  }
  // 结束游戏
  gameover( winner ) {

    this.isStart = false;
    this.isOver = true;
    this.winner = winner;

    // 因为state没有修改 所以需要强制一次更新
    this.forceUpdate();

  }
  // 重置
  reset() {

    const rows = parseInt(this.state.rows);
    const cols = parseInt(this.state.cols);

    this.init( rows, cols );

    if( this.gameTimer ) {
      window.clearInterval( this.gameTimer );
    }
    this.setState({ 
      gameClock:0,
    });

  }
  // 检测行
  isFiveInARow( row, board ) {

    if( row < 0 || row >= board.length ) return -1;

    var piece = -1;
    var count = 0;
    const cols = board[row].length;

    for( var i = 0 ; i <= cols; i++ ) {
      if( (board[row][i] % 2) === piece && piece >= 0 ){
        count++;
        if( count >= 5 ){
          for( var j = 0; j < 5; j++ ) {
            board[row][i-j] = FIR_CODE + piece;
          }
          // 标记超过5个的棋子
          i++;
          while( i <= cols && board[row][i] === piece ){
            board[row][i] = FIR_CODE + piece;
            i++;
          }
          return piece;
        } 
      }
      else {
        piece = (board[row][i] % 2);
        count = 1;
      }
    }
    
    return -1;

  }
  // 检测列
  isFiveInACol( col, board ) {

    var piece = -1;
    var count = 0;
    const rows = parseInt(this.state.rows);

    for( var i = 0 ; i <= rows; i++ ) {
      if( (board[i][col] % 2) === piece && piece >= 0 ){
        count++;
        if( count >= 5 ) {
          for( var j = 0; j < 5; j++ ) {
            board[i - j][col] = FIR_CODE + piece;
          }
          i++;
          while( i <= rows && board[i][col] === piece ){
            board[i][col] = FIR_CODE + piece;
            i++;
          }
          return piece;
        } 
      }
      else {
        piece = (board[i][col] % 2);
        count = 1;
      }
    }
    
    return -1;

  }
  // 检测对角线
  isFiveInLeftDiagonal( row, col, board ) {

    const rows = parseInt(this.state.rows);
    const cols = parseInt(this.state.cols);
    var piece = -1;
    var count = 0;

    var i, j, k;
    var r = row;
    var c = col;

    while( r > 0 ) {
      r--;
      c--;
    }

    for(i = r, j = c ; i <= rows && j <= cols; i++, j++ ) {
      if( (board[i][j] % 2) === piece && piece >= 0 ){
        count++;
        if( count >= 5 ){
          for( k = 0; k < 5; k++ ) {
            board[i - k][j - k] = FIR_CODE + piece;
          }
          i++;j++;
          while( i <= rows && j <= cols && board[i][j] === piece){
            board[i][j] = FIR_CODE + piece;
            i++;j++;
          }
          return piece;
        } 
      }
      else {
        piece = (board[i][j] % 2);
        count = 1;
      }
    }

  }

  isFiveInRightDiagonal( row, col, board ) {

    const rows = parseInt(this.state.rows);
    var piece = -1;
    var count = 0;
    var r = row;
    var c = col;
    var i,j,k;

    while( r > 0 ) {
      r--;
      c++;
    }

    for( i = r, j = c ; i <= rows && j >= 0; i++, j-- ) {
      if( (board[i][j] % 2) === piece && piece >= 0 ){
        count++;
        if( count >= 5 ){
          for( k = 0; k < 5; k++ ) {
            board[i - k][j + k] = FIR_CODE + piece;
          }
          i++;j--;
          while( i <= rows && j >= 0 && board[i][j] === piece){
            board[i][j] = FIR_CODE + piece;
            i++;j--;
          }
          return piece;
        } 
      }
      else {
        piece = (board[i][j] % 2);
        count = 1;
      }
    }

    return -1;

  }
  // 检测对应点是否有5子连线
  check( r, c ) {

    var isOver = false;
    const winner = (this.movements.length - 1) % 2;
    const board = this.board;

    if( this.isFiveInARow( r, board ) >= 0 ) {
      isOver = true;
    }

    if( this.isFiveInACol( c, board ) >= 0 ) {
      isOver = true;
    }
    
    if( this.isFiveInLeftDiagonal( r, c, board ) >= 0 ) {
      isOver = true;
    }

    if( this.isFiveInRightDiagonal( r, c, board ) >= 0 ) {
      isOver = true;
    }

    if( isOver ) {
      this.gameover( winner );
    }
  }
  // 连接服务器
  connect(event) {
    const url = this.state.server;
    const room = this.state.room;
    const name = this.state.myName;
    if( this.socket ) {
      const state = this.socket.readyState;
      if( state === 1 ) return;
    }
    const socket = new WebSocket(`ws://${url}/gobang:${name}:${room}`);

    socket.onopen = () =>{
      socket.send('GET_MOVEMENTS:');
    }

    socket.onmessage = ( event ) =>{
      const data = event.data;
      const cmd = data.substring(0, data.indexOf(':'));
      const content = data.substring(data.indexOf(':') + 1);
      console.log( 'ws:', data );
      switch(cmd){
        case 'LIST_PLAYERS':
          // console.log( 'content:', content );
          const players = JSON.parse(content);
          // console.log('player:', players );
          // 设置比赛选手
          this.playerA = {name:"",key:"A", isBlack:true};
          this.playerB = {name:"",key:"B", isBlack:false};
          players.forEach((player) => {
            if( player.role === BLACK_CODE ) {
              this.playerA.name = player.name;
              this.playerA.isBlack = true;
              this.playerA.key = player.key;
            }

            if( player.role === WHILE_CODE ){
              this.playerB.name = player.name;
              this.playerB.isBlack = false;
              this.playerB.key = player.key;
            }

            if( this.myKey === player.key ) {
              this.setState({role: player.role});
            }

          });
          this.setState({ players: players });
          break;
        case 'KEY':
          this.myKey = content;
          break;
        case 'MOVEMENTS':
          const movements = JSON.parse(content);
          // console.log(movements)
          this.reset();
          for(var i = 0, il = movements.length; i < il; i++ ) {
            this.move(movements[i].row, movements[i].col, movements[i].value);
          }
          break;
        case 'MOVE':

          const movement = JSON.parse(content);
          const row = parseInt(movement.row);
          const col = parseInt(movement.col);
          const value = parseInt(movement.value);
          this.move(row, col, value);
          
          break;
        default:
          break;
      }
    }
    socket.onclose = ()=>{
      this.myKey = "";
      this.setState({ 
        role:-1,
        players: [] 
      });
    }

    this.socket = socket;
  }

  disconnect() {
    if( this.isConnect()) {
      this.socket.close();
    }
  }

  isConnect(){
    if( this.socket && this.socket.readyState === 1 ) {
      return true;
    }
    return false;
  }
  // 重新开始
  restart(){
    if( this.isConnect() ){
      this.socket.send('RESET:');
    }
  }

  // 设置角色
  setRole( event ) {
    const role = parseInt( event.target.value );
    console.log(role);
    if( !this.isConnect() ) return;
    const msg = `SET_ROLE:${role}`;
    this.socket.send(msg);
  }

  // 检测是否可落子
  isMyTurn( isPlayerA = true ) {
    const isBlack = (this.movements.length % 2 === BLACK_CODE);

    return(
      isPlayerA ? (this.playerA.isBlack === isBlack)
      : (this.playerB.isBlack === isBlack)
    );
  }
  // 下棋
  handlePieceClicked(event) {
    const pos = event.target.getAttribute('pos').split('-');
    const mode = parseInt( this.state.mode );
    const movements = this.movements;
    const r = parseInt(pos[0]);
    const c = parseInt(pos[1]);
    const v = movements.length % 2;

    if( mode === PLAY_MODE.SINGLE ){
      this.move( r, c, v );
    }
    else if( mode === PLAY_MODE.PVP ){
      var canMove = false;
      var isBlack = true;

      if( this.myKey === this.playerA.key ){
        canMove = this.isMyTurn();
        isBlack = this.playerA.isBlack;
      }
      else if( this.myKey === this.playerB.key ){
        canMove = this.isMyTurn(false);
        isBlack = this.playerB.isBlack;
      }

      if( this.board[r][c] !== DEFAULT_CODE ) {
        canMove = false;
      }

      if( canMove ){
        const movement = { row: r, col: c, value:isBlack?BLACK_CODE:WHILE_CODE };
        if( this.isConnect() ){
          this.socket.send(`MOVE:${JSON.stringify(movement)}`);
        }
      }
      // if( this.isMyTurn() ){
      //   this.move( pos[0], pos[1], movements.length % 2 );
      // }
    }
    else if( mode === PLAY_MODE.PVAI ) {
      if( this.isMyTurn() ){
        if( this.move( r, c, v ) ){
          this.aiMove();
        }
      }
    }
  }
  // 绘制棋盘
  drawBoard() {
    const rows = parseInt(this.state.rows);
    const cols = parseInt(this.state.cols);
    const size = parseInt(this.state.size);
    const middle = size / 2;
    const steps = this.steps;

    return this.board.map( ( row, r ) =>
      <div className='board-row' key={r}>
        {
          row.map( (col, c) => {
            const strokeColor = Math.floor( col / FIR_CODE ) > 0 ? "#FF0000" : "#000";
            const isBlack = ((col % 2) === BLACK_CODE);
            return(
              <svg style={{minWidth:`${size}px`,minHeight:`${size}px`}} 
                width={size} height={size}
                className='piece' key={c} pos={`${r}-${c}`} 
                onClick={this.handlePieceClicked}
              >
                <line strokeWidth={2} id={`h-${r}`} pos={`${r}-${c}`}
                  y1={middle} x1={c===0?middle:0} 
                  y2={middle} x2={c===cols?middle:size} 
                  stroke="#000" fill="none"/>
                <line strokeWidth={2} id={`v-${c}`} pos={`${r}-${c}`}
                  y1={r===0?middle:0} x1={middle} 
                  y2={r===rows?middle:size} x2={middle} 
                  stroke="#000" fill="none"/>
                {
                  col>=0
                    ? <circle id="piece" pos={`${r}-${c}`}
                      r={middle * 0.8} 
                      cy={middle} cx={middle} 
                      stroke={strokeColor} strokeWidth={2} 
                      fill={isBlack ?"#000":"#ffffff"}/>
                    : ""
                }
                {
                  this.state.showSteps && steps[r][c] > 0
                    ? <text id="text" pos={`${r}-${c}`} fontSize={middle}
                        x={middle} y={middle * 1.25} style={{textAnchor: "middle"}}
                        stroke={ isBlack ?"#ffffff":"#000"} strokeWidth={1}
                        fill={ isBlack ?"#ffffff":"#000"}>{steps[r][c]}
                      </text>
                    : ""
                }
              </svg>
            )
          })
        }
      </div>
    )
  }
  // 绘制单个棋子
  drawPiece( size, isBlack, onClicked ) {

    return(
      <svg style={{margin:`5px auto 5px auto`, minWidth:`${size}px`,minHeight:`${size}px`}} 
        width={size} height={size}
      >
        <rect strokeWidth={2}
          width={size} height={size} 
          stroke="#000" fill="none"
        />
        <circle
          value={isBlack}
          onClick={onClicked}
          r={ size * 0.25 } 
          cy={size / 2} cx={ size / 2} 
          stroke={isBlack?"none":"#000"} strokeWidth={1} 
          fill={isBlack?"#000":"#ffffff"}
        />
      </svg>
    );
  }
  // 绘制角色
  drawPlayer() {
    const mode = parseInt(this.state.mode);

    if( mode === PLAY_MODE.SINGLE ) return;

    const size = parseInt( this.state.size );
    const playerA = this.playerA;
    const playerB = this.playerB;
    
    return(
      <div className='gobang-player'>
        {this.drawPiece( size, playerA.isBlack, this.handlePlayerChanged)}
        <label>{playerA.name}{playerA.key && playerA.key === this.myKey ? "[*]":""}</label>
        <div className='fullfill'>{"<= VS =>"}</div>
        <label>{playerB.name}{playerB.key && playerB.key === this.myKey ? "[*]":""}</label>
        {this.drawPiece( size, playerB.isBlack)}
      </div>
    )
  }
  // 绘制服务器
  drawServer() {
    const mode = parseInt( this.state.mode );
    if( mode !== PLAY_MODE.PVP ) return;

    return(
      <div className='gobang-server'>
        <div className='row'>
          <label>服务器:</label>
          <input type='text' id='server' value={this.state.server} onChange={this.handleValueChanged}/>
          <label>房间号:</label>
          <input type='text' id='room' value={this.state.room} onChange={this.handleValueChanged} />
        </div>
        <div className='row'>
          <label>游戏名:</label>
            <input type='text' id='name' value={this.state.myName} onChange={this.handleValueChanged} />
            
            <button onClick={this.connect} disabled={this.isConnect()}>进入</button>
        </div>
        <div className='row'>
          <label>角色选择:</label>
          <input type='radio' id='role0' name='role' value={0} onChange={this.setRole} checked={this.state.role===0}/>
          <label>黑子</label>
          <input type='radio' id='role1' name='role' value={1} onChange={this.setRole} checked={this.state.role===1}/>
          <label>白子</label>
          <input type='radio' id='role' name='role' value={-1} onChange={this.setRole} checked={this.state.role===-1}/>
          <label>旁观</label>
          <button onClick={this.disconnect} disabled={!this.isConnect()}>离开</button>
        </div>
        <div className='row'>
          <label className='col align-right'>玩家列表:</label>
          <button onClick={this.restart} disabled={this.myKey !== this.playerA.key}>重新开始</button>
        </div>
        {this.state.players.map((item, key)=>{
          return (<label className='align-left' key={key}>{item.name}[{item.key}]{item.key === this.myKey ? "*":""}</label>);
        })}
      </div>
    );
  }
  //切换步骤显示
  switchStepsDisplay(){
    this.setState({ showSteps: !this.state.showSteps });
  }

  render(){
    const isStart = this.isStart;
    const isOver = this.isOver;
    const movements = this.movements;
    const mode = parseInt( this.state.mode );
    const unmoveable = movements.length > 0 && mode !== PLAY_MODE.PVP;
    const winner = this.winner;

    return(
      <div className='gobang'>
        <div className='board'>
          { this.drawBoard() }
          <form className='gobang-mode' onChange={this.handleModeChanged}>
            <label>单人模式</label>
            <input type='radio' name='mode' value={PLAY_MODE.SINGLE} defaultChecked/>
            <label>双人对战</label>
            <input type='radio' name='mode' value={PLAY_MODE.PVP}/>
            <label>人机对弈</label>
            <input type='radio' name='mode' value={PLAY_MODE.PVAI}/>
          </form>
          {this.drawPlayer()}
          {this.drawServer()} 
        </div>   
        <div className='gobang-menu'>
          <div className='control'>
            <button onClick={this.start.bind(this)} disabled={isStart && this.state.mode === PLAY_MODE.PVP}>开始</button>
            <button onClick={this.reset.bind(this)} disabled={!isStart || this.state.mode === PLAY_MODE.PVP}>重置</button>
            <button onClick={this.unmove.bind(this)} disabled={ isOver || !unmoveable}>悔棋</button>
          </div>
          <label>下一步</label>
          <div className='result'>{this.drawPiece( parseInt(this.state.size) * 2, (movements.length + 1) % 2 )}</div>
          <label>步数</label>
          <div className='result' onClick={this.switchStepsDisplay.bind(this)} >{movements.length}</div>
          <label>胜方</label>
          <div className='result'>{isOver?winner===0?"黑子":"白子":"未结束"}</div>
          <label>计时器</label>
          <div className='result'>{this.toTimeString(this.state.gameClock)}</div>
        </div>
      </div>
    )

  }

}